package cn.yeziji.utils;

import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * @author gzkemays
 * @date 2021/4/16 17:06
 */
@Component
public class MinIoUtils {

  private static String ENDPOINT;
  private static String ACCESS_KEY;
  private static String secretKey;
  private static String imgBucket;
  private static String CHUNK_BUCKET = "chunk";
  private static MinioClient CLIENT;

  /** 排序 */
  public static final boolean SORT = true;
  /** 不排序 */
  public static final boolean NOT_SORT = false;
  /** 删除分片 */
  public static final boolean DELETE_CHUNK_OBJECT = true;
  /** 不删除分片 */
  public static final boolean NOT_DELETE_CHUNK_OBJECT = false;
  /** 默认过期时间(分钟) */
  private static final Integer DEFAULT_EXPIRY = 60;

  public MinIoUtils(String accessKey, String secretKey, String endPoint) {
    CLIENT = MinioClient.builder().credentials(accessKey, secretKey).endpoint(endPoint).build();
    // 方便管理分片文件，则单独创建一个分片文件的存储桶
    if (!isBucketExist(CHUNK_BUCKET)) {
      createBucket(CHUNK_BUCKET);
    }
  }

  /**
   * 存储桶是否存在
   *
   * @param bucketName 存储桶名称
   * @return true/false
   */
  @SneakyThrows
  public static boolean isBucketExist(String bucketName) {
    return CLIENT.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
  }

  /**
   * 创建存储桶
   *
   * @param bucketName 存储桶名称
   * @return true/false
   */
  @SneakyThrows
  public static boolean createBucket(String bucketName) {
    CLIENT.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
    return true;
  }

  /**
   * 获取访问对象的外链地址
   *
   * @param bucketName 存储桶名称
   * @param objectName 对象名称
   * @param expiry 过期时间(分钟) 最大为7天 超过7天则默认最大值
   * @return viewUrl
   */
  @SneakyThrows
  public String getObjectUrl(String bucketName, String objectName, Integer expiry) {
    expiry = expiryHandle(expiry);
    return CLIENT.getPresignedObjectUrl(
        GetPresignedObjectUrlArgs.builder()
            .method(Method.GET)
            .bucket(bucketName)
            .object(objectName)
            .expiry(expiry)
            .build());
  }

  /**
   * 创建上传文件对象的外链
   *
   * @param bucketName 存储桶名称
   * @param objectName 欲上传文件对象的名称
   * @param expiry 过期时间(分钟) 最大为7天 超过7天则默认最大值
   * @return uploadUrl
   */
  @SneakyThrows
  public String createUploadUrl(String bucketName, String objectName, Integer expiry) {
    expiry = expiryHandle(expiry);
    return CLIENT.getPresignedObjectUrl(
        GetPresignedObjectUrlArgs.builder()
            .method(Method.PUT)
            .bucket(bucketName)
            .object(objectName)
            .expiry(expiry)
            .build());
  }

  /**
   * 创建上传文件对象的外链
   *
   * @param bucketName 存储桶名称
   * @param objectName 欲上传文件对象的名称
   * @return uploadUrl
   */
  public String createUploadUrl(String bucketName, String objectName) {
    return createUploadUrl(bucketName, objectName, DEFAULT_EXPIRY);
  }

  /**
   * 批量创建分片上传外链
   *
   * @param bucketName 存储桶名称
   * @param objectMD5 欲上传分片文件主文件的MD5
   * @param chunkCount 分片数量
   * @return uploadChunkUrls
   */
  public List<String> createUploadChunkUrlList(
      String bucketName, String objectMD5, Integer chunkCount) {
    if (null == bucketName) {
      bucketName = CHUNK_BUCKET;
    }
    if (null == objectMD5) {
      return null;
    }
    objectMD5 += "/";
    if (null == chunkCount || 0 == chunkCount) {
      return null;
    }
    List<String> urlList = new ArrayList<>(chunkCount);
    for (int i = 1; i <= chunkCount; i++) {
      String objectName = objectMD5 + i + ".chunk";
      urlList.add(createUploadUrl(bucketName, objectName, DEFAULT_EXPIRY));
    }
    return urlList;
  }

  /**
   * 创建指定序号的分片文件上传外链
   *
   * @param bucketName 存储桶名称
   * @param objectMD5 欲上传分片文件主文件的MD5
   * @param partNumber 分片序号
   * @return uploadChunkUrl
   */
  public String createUploadChunkUrl(String bucketName, String objectMD5, Integer partNumber) {
    if (null == bucketName) {
      bucketName = CHUNK_BUCKET;
    }
    if (null == objectMD5) {
      return null;
    }
    objectMD5 += "/" + partNumber + ".chunk";
    return createUploadUrl(bucketName, objectMD5, DEFAULT_EXPIRY);
  }

  /**
   * 获取对象文件名称列表
   *
   * @param bucketName 存储桶名称
   * @param prefix 对象名称前缀
   * @param sort 是否排序(升序)
   * @return objectNames
   */
  @SneakyThrows
  public List<String> listObjectNames(String bucketName, String prefix, Boolean sort) {
    ListObjectsArgs listObjectsArgs;
    if (null == prefix) {
      listObjectsArgs = ListObjectsArgs.builder().bucket(bucketName).recursive(true).build();
    } else {
      listObjectsArgs =
          ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(true).build();
    }
    Iterable<Result<Item>> chunks = CLIENT.listObjects(listObjectsArgs);
    List<String> chunkPaths = new ArrayList<>();
    for (Result<Item> item : chunks) {
      chunkPaths.add(item.get().objectName());
    }
    if (sort) {
      chunkPaths.sort(new Str2IntComparator(false));
    }
    return chunkPaths;
  }

  /**
   * 获取对象文件名称列表
   *
   * @param bucketName 存储桶名称
   * @param prefix 对象名称前缀
   * @return objectNames
   */
  public List<String> listObjectNames(String bucketName, String prefix) {
    return listObjectNames(bucketName, prefix, NOT_SORT);
  }

  /**
   * 获取分片文件名称列表
   *
   * @param bucketName 存储桶名称
   * @param ObjectMd5 对象Md5
   * @return objectChunkNames
   */
  public List<String> listChunkObjectNames(String bucketName, String ObjectMd5) {
    if (null == bucketName) {
      bucketName = CHUNK_BUCKET;
    }
    if (null == ObjectMd5) {
      return null;
    }
    return listObjectNames(bucketName, ObjectMd5, SORT);
  }

  /**
   * 获取分片名称地址HashMap key=分片序号 value=分片文件地址
   *
   * @param bucketName 存储桶名称
   * @param ObjectMd5 对象Md5
   * @return objectChunkNameMap
   */
  public Map<Integer, String> mapChunkObjectNames(String bucketName, String ObjectMd5) {
    if (null == bucketName) {
      bucketName = CHUNK_BUCKET;
    }
    if (null == ObjectMd5) {
      return null;
    }
    List<String> chunkPaths = listObjectNames(bucketName, ObjectMd5);
    if (null == chunkPaths || chunkPaths.size() == 0) {
      return null;
    }
    Map<Integer, String> chunkMap = new HashMap<>(chunkPaths.size());
    for (String chunkName : chunkPaths) {
      Integer partNumber =
          Integer.parseInt(
              chunkName.substring(chunkName.indexOf("/") + 1, chunkName.lastIndexOf(".")));
      chunkMap.put(partNumber, chunkName);
    }
    return chunkMap;
  }

  /**
   * 删除对象
   *
   * @param bucketName
   * @param objectName
   * @return true/false
   */
  @SneakyThrows
  public boolean removeObject(String bucketName, String objectName) {
    CLIENT.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
    return true;
  }

  /**
   * 批量删除对象
   *
   * @param bucketName
   * @param objectNames
   * @return true/false
   */
  public boolean removeObjects(String bucketName, List<String> objectNames) {
    List<DeleteObject> deleteObjects = new ArrayList<>(objectNames.size());
    for (String objectName : objectNames) {
      deleteObjects.add(new DeleteObject(objectName));
    }
    CLIENT.removeObjects(
        RemoveObjectsArgs.builder().bucket(bucketName).objects(deleteObjects).build());
    return true;
  }

  /**
   * 合并分片文件成对象文件
   *
   * @param CHUNK_BUCKETName 分片文件所在存储桶名称
   * @param composeBucketName 后的对象文件合并存储的存储桶名称
   * @param chunkNames 分片文件名称集合
   * @param objectName 合并后的对象文件名称
   * @return true/false
   */
  @SneakyThrows
  public boolean composeObject(
      String CHUNK_BUCKETName,
      String composeBucketName,
      List<String> chunkNames,
      String objectName,
      boolean isDeleteChunkObject) {
    if (null == CHUNK_BUCKETName) {
      CHUNK_BUCKETName = CHUNK_BUCKET;
    }
    List<ComposeSource> sourceObjectList = new ArrayList<>(chunkNames.size());
    for (String chunk : chunkNames) {
      sourceObjectList.add(ComposeSource.builder().bucket(CHUNK_BUCKETName).object(chunk).build());
    }
    CLIENT.composeObject(
        ComposeObjectArgs.builder()
            .bucket(composeBucketName)
            .object(objectName)
            .sources(sourceObjectList)
            .build());
    if (isDeleteChunkObject) {
      removeObjects(CHUNK_BUCKETName, chunkNames);
    }
    return true;
  }

  /**
   * 合并分片文件成对象文件
   *
   * @param bucketName 存储桶名称
   * @param chunkNames 分片文件名称集合
   * @param objectName 合并后的对象文件名称
   * @return true/false
   */
  public boolean composeObject(String bucketName, List<String> chunkNames, String objectName) {
    return composeObject(CHUNK_BUCKET, bucketName, chunkNames, objectName, NOT_DELETE_CHUNK_OBJECT);
  }

  /**
   * 合并分片文件成对象文件
   *
   * @param bucketName 存储桶名称
   * @param chunkNames 分片文件名称集合
   * @param objectName 合并后的对象文件名称
   * @return true/false
   */
  public boolean composeObject(
      String bucketName, List<String> chunkNames, String objectName, boolean isDeleteChunkObject) {
    return composeObject(CHUNK_BUCKET, bucketName, chunkNames, objectName, isDeleteChunkObject);
  }

  /**
   * 合并分片文件，合并成功后删除分片文件
   *
   * @param bucketName 存储桶名称
   * @param chunkNames 分片文件名称集合
   * @param objectName 合并后的对象文件名称
   * @return true/false
   */
  public boolean composeObjectAndRemoveChunk(
      String bucketName, List<String> chunkNames, String objectName) {
    return composeObject(CHUNK_BUCKET, bucketName, chunkNames, objectName, DELETE_CHUNK_OBJECT);
  }

  /**
   * 将分钟数转换为秒数
   *
   * @param expiry 过期时间(分钟数)
   * @return expiry
   */
  private int expiryHandle(Integer expiry) {
    expiry = expiry * 60;
    if (expiry > 604800) {
      return 604800;
    }
    return expiry;
  }

  static class Str2IntComparator implements Comparator<String> {
    private final boolean reverseOrder; // 是否倒序

    public Str2IntComparator(boolean reverseOrder) {
      this.reverseOrder = reverseOrder;
    }

    @Override
    public int compare(String arg0, String arg1) {
      Integer intArg0 =
          Integer.parseInt(arg0.substring(arg0.indexOf("/") + 1, arg0.lastIndexOf(".")));
      Integer intArg1 =
          Integer.parseInt(arg1.substring(arg1.indexOf("/") + 1, arg1.lastIndexOf(".")));
      if (reverseOrder) {
        return intArg1 - intArg0;
      } else {
        return intArg0 - intArg1;
      }
    }
  }
}
